package cn.org.xwb.common.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * 和 AOP 面向切面相关的工具类
 */
public class AspectUtil {

    private AspectUtil() {
    }

    /**
     * 函数式接口，参数根据注解进行处理的处理器
     */
    @FunctionalInterface
    public interface ParamHandlerByAnnotation<T> {

        /**
         * 根据参数的注解进行方法参数的处理
         *
         * @param arg 方法参数
         * @param annotations 当前方法参数的所有注解
         * @return 处理结果
         */
        T doParamHandleByAnnotation(Object arg, Annotation[] annotations);

    }

    /**
     * 函数式接口，进行对象形式的方法参数的处理的处理器
     */
    @FunctionalInterface
    public interface ObjParamHandler<T> {

        /**
         * 对对象形式的方法参数进行处理
         *
         * @param object 对象形式的方法参数
         */
        T doObjParamHandle(Object object);

    }

    /**
     * 函数式接口，进行方法参数的处理的处理器
     */
    @FunctionalInterface
    public interface ParamHandler<T> {

        /**
         * 对方法参数进行处理
         *
         * @param param 方法参数
         * @return 处理结果
         */
        T doParamHandle(Object param);

    }

    /**
     * 函数式接口，根据指定的注解进行方法参数的处理的处理器
     */
    @FunctionalInterface
    public interface ParamHandlerWithAnno<T> {

        /**
         * 根据指定的注解对方法参数进行处理
         *
         * @param param 方法参数
         * @param anno 注解
         * @return 处理结果
         */
        T doParamHandleWithAnno(Object param, Annotation anno);

    }

    /**
     * 获取切点方法所有参数的所有注解
     *
     * @param joinPoint 切点
     * @return 切点方法所有参数的所有注解
     */
    public static Annotation[][] getPointMethodParamAnnotations(JoinPoint joinPoint) {
        // 获取切点方法的签名
        MethodSignature pointMethodSignature = (MethodSignature) joinPoint.getSignature();
        // 获取切点方法的反射
        Method pointMethod = pointMethodSignature.getMethod();
        // 获取切点方法所有参数的所有注解
        return pointMethod.getParameterAnnotations();
    }

    /**
     * 执行对切点方法的所有参数的相应处理
     *
     * @param joinPoint                切点
     * @param paramHandlerByAnnotation 每个参数根据注解对参数进行处理的处理器
     * @return 处理后的参数数组
     */
    public static Object[] doPointMethodAllParamHandle(
            JoinPoint joinPoint,
            ParamHandlerByAnnotation<?> paramHandlerByAnnotation
    ) {
        // 获取切点方法的参数
        Object[] args = joinPoint.getArgs();
        // 获取切点方法的所有参数的所有注解
        Annotation[][] pointMethodParameterAnnotations = getPointMethodParamAnnotations(joinPoint);
        // 遍历切点方法中的参数，根据参数的注解进行相应的处理
        for (int i = 0; i < pointMethodParameterAnnotations.length; i++) {
            // 当前遍历的参数上的注解
            Annotation[] pointMethodParameterAnnotation = pointMethodParameterAnnotations[i];
            // 根据注解对当前遍历的参数进行处理
            args[i] = paramHandlerByAnnotation.doParamHandleByAnnotation(args[i], pointMethodParameterAnnotation);
        }
        // 返回处理后的参数数组
        return args;
    }

    /**
     * 执行方法所有参数处理的环绕通知增强代码
     *
     * @param proceedingJoinPoint     切点方法
     * @param objParamAnnotationClass 标注需要进行处理的对象形式参数的注解
     * @param objParamHandler         进行对象形式参数处理的方法
     * @param paramAnnotationClass    标注需要进行处理的参数的注解
     * @param paramHandler            进行参数处理的方法
     * @return 切点方法的执行结果
     */
    public static Object doAllParamHandleAroundAdvice(
            ProceedingJoinPoint proceedingJoinPoint,
            Class<? extends Annotation> objParamAnnotationClass,
            ObjParamHandler<?> objParamHandler,
            Class<? extends Annotation> paramAnnotationClass,
            ParamHandler<?> paramHandler
    ) throws Throwable {
        // 执行对切点方法的所有参数的处理
        Object[] args = doPointMethodAllParamHandle(
                proceedingJoinPoint,
                // arg 当前需要处理的参数
                // annotations 当前需要处理的参数的注解
                (Object arg, Annotation[] annotations) -> {
                    // 当前参数上具有"需要进行对象形式的方法参数处理"注解，进行方法参数对象处理
                    if (ReflectUtil.judgeContainAnnotation(annotations, objParamAnnotationClass)) {
                        // 进行方法参数对象处理
                        objParamHandler.doObjParamHandle(arg);
                    }
                    // 当前数上具有"需要进行方法参数处理"注解，进行方法参数处理
                    else if (ReflectUtil.judgeContainAnnotation(annotations, paramAnnotationClass)) {
                        // 进行方法参数处理
                        arg = paramHandler.doParamHandle(arg);
                    }
                    // 返回处理过后的当前参数
                    return arg;
                }
        );
        // 执行切点方法，同时传递方法参数，并返回切点方法的返回结果
        return proceedingJoinPoint.proceed(args);
    }

    /**
     * 执行方法参数处理的前置通知增强代码
     *
     * @param joinPoint               切点方法
     * @param objParamAnnotationClass 标注需要进行处理的对象形式参数的注解
     * @param objParamHandler         进行对象形式参数处理的方法
     * @param paramAnnotationClass    标注需要进行处理的参数的注解
     * @param paramHandler            进行参数处理的方法
     */
    public static Object[] doParamHandleBeforeAdvice(
            JoinPoint joinPoint,
            Class<? extends Annotation> objParamAnnotationClass,
            ObjParamHandler<?> objParamHandler,
            Class<? extends Annotation> paramAnnotationClass,
            ParamHandler<?> paramHandler
    ) {
        // 执行对切点方法的所有参数的处理
        // 返回每个参数的处理结果
        return doPointMethodAllParamHandle(
                joinPoint,
                // arg 当前需要处理的参数
                // annotations 当前需要处理的参数的注解
                (Object arg, Annotation[] annotations) -> {
                    // 存储当前参数的处理结果
                    Object res = null;
                    // 当前参数上具有"需要进行对象形式的方法参数处理"注解，进行方法参数对象处理
                    if (ReflectUtil.judgeContainAnnotation(annotations, objParamAnnotationClass)) {
                        // 进行方法参数对象处理
                        res = objParamHandler.doObjParamHandle(arg);
                    }
                    // 当前数上具有"需要进行方法参数处理"注解，进行方法参数处理
                    else if (ReflectUtil.judgeContainAnnotation(annotations, paramAnnotationClass)) {
                        // 进行方法参数处理
                        res = paramHandler.doParamHandle(arg);
                    }
                    // 返回当前参数的处理结果
                    return res;
                }
        );
    }

    /**
     * 执行方法参数处理的前置通知增强代码
     *
     * @param joinPoint               切点方法
     * @param objParamAnnotationClass 标注需要进行处理的对象形式参数的注解
     * @param objParamHandler         进行对象形式参数处理的方法
     * @param paramAnnotationClass    标注需要进行处理的参数的注解
     * @param paramHandlerWithAnno            进行参数处理的方法
     */
    public static Object[] doParamHandleBeforeAdvice(
            JoinPoint joinPoint,
            Class<? extends Annotation> objParamAnnotationClass,
            ObjParamHandler<?> objParamHandler,
            Class<? extends Annotation> paramAnnotationClass,
            ParamHandlerWithAnno<?> paramHandlerWithAnno
    ) {
        // 执行对切点方法的所有参数的处理
        // 返回每个参数的处理结果
        return doPointMethodAllParamHandle(
                joinPoint,
                // arg 当前需要处理的参数
                // annotations 当前需要处理的参数的注解
                (Object arg, Annotation[] annotations) -> {
                    // 存储当前参数的处理结果
                    Object res = null;
                    // 当前参数上具有"需要进行对象形式的方法参数处理"注解，进行方法参数对象处理
                    if (ReflectUtil.judgeContainAnnotation(annotations, objParamAnnotationClass)) {
                        // 进行方法参数对象处理
                        res = objParamHandler.doObjParamHandle(arg);
                    }
                    // 当前数上具有"需要进行方法参数处理"注解，进行方法参数处理
                    else if (ReflectUtil.judgeContainAnnotation(annotations, paramAnnotationClass)) {
                        // 进行方法参数处理
                        Annotation anno = ReflectUtil.getAnnotation(annotations, paramAnnotationClass);
                        res = paramHandlerWithAnno.doParamHandleWithAnno(arg, anno);
                    }
                    // 返回当前参数的处理结果
                    return res;
                }
        );
    }

    /**
     * 获取切点方法的方法名
     *
     * @param joinPoint 切点方法
     * @return 切点方法的方法名
     */
    public static String getPointMethodName(JoinPoint joinPoint) {
        return joinPoint.getSignature().getName();
    }

    /**
     * 获取切点方法的所有参数名
     *
     * @param joinPoint 切点方法
     * @return 切点方法的所有参数名
     */
    public static String[] getPointMethodParamNames(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        return methodSignature.getParameterNames();
    }

    /**
     * 获取切点方法的所有参数的 Class
     *
     * @param joinPoint 切点方法
     * @return 切点方法的所有参数的 Class
     */
    public static Class[] getPointMethodParams(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        return methodSignature.getParameterTypes();
    }

    /**
     * 获取切点方法的字符串形式的签名
     *
     * @param joinPoint 切点
     * @return 切点方法的字符串形式的签名
     */
    public static String getPointMethodSignatureStr(JoinPoint joinPoint) {
        StringBuilder methodSignatureStr = new StringBuilder();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 切点方法的返回类型
        methodSignatureStr.append(methodSignature.getReturnType().getName());
        methodSignatureStr.append(" ");
        // 切点方法的方法名
        methodSignatureStr.append(getPointMethodName(joinPoint));
        methodSignatureStr.append("(");
        Class[] pointMethodParams = getPointMethodParams(joinPoint);
        String[] pointMethodParamNames = getPointMethodParamNames(joinPoint);
        for (int i=0; i<pointMethodParams.length; i++) {
            methodSignatureStr.append(pointMethodParams[i].getName());
            methodSignatureStr.append(" ");
            methodSignatureStr.append(pointMethodParamNames[i]);
            if (i == pointMethodParams.length - 1) break;
            methodSignatureStr.append(", ");
        }
        methodSignatureStr.append(")");
        return methodSignatureStr.toString();
    }

    /**
     * 根据参数名获取切点方法指定参数的值，找不到返回 null
     *
     * @param joinPoint 切点
     * @return 指定参数的值
     */
    public static Object getPointMethodParamValByName(JoinPoint joinPoint, String paramName) {
        // 获取切点方法的所有参数的值
        Object[] args = joinPoint.getArgs();
        // 获取切点方法的所有参数名
        String[] pointMethodParamNames = getPointMethodParamNames(joinPoint);
        for (int i = 0; i < pointMethodParamNames.length; i++) {
            // 找到指定参数
            if (pointMethodParamNames[i].equals(paramName)) return args[i];
        }
        return null;
    }

}
